﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using élénkPróbája.extentions;
using élénkPróbája.Test.dto;
using élénkPróbája.Test.dto.@interface;
using élénkPróbája.Test.extention;
using élénkPróbája.Test.helpers;
using élénkPróbája.validators;

namespace élénkPróbája.Test
{
    [TestClass]
    public class CollectionValidatorRelativeTests
    {
        Company company;

        public CollectionValidatorRelativeTests()
        {
            company = new Company()
            {
                Id = 1,
                Name = "FirstMerit",
                Owner = new Person()
                {
                    Id=1,
                    Forename = "Jason",
                    Surname = "Smith"
                },
                SubCompanies = new List<Company>()
                {
                    new Company() { Id=100, Name = "SecondMerit" },
                    new Company() { Id = 200, Name = "FirstMerit"}
                },
                Accounts = new List<Account>() {
                    new Account() { AccountName = "FirstMerit", Balance = 100},
                    new Account { AccountName = "kevin@email.com"}
                },
                Employees = new List<Person>()
                {
                    new Person() {Id=1,Forename = "Jason", Surname = "Miller", Email = "jason@email.com"},
                    new Person() {Id = 2, Forename = "Kevin", Surname = "Scheutzow", Email = "kevin@email.com"},
                    new Person() {Id = 3,Forename = "Matt", Surname = "Wagers", Email = "matt@email.com"} 
                }
           };
        }

        [TestMethod]
        public void Validates_collection()
        {
            var validator = new TestValidator {
                v => v.RuleFor(x => x.Name).NotNull(),
                v => v.RuleFor(x => x.Accounts).SetCollectionValidator(y => new  AccountValidator(y))
            };

            var results = validator.Validate(company);
            results.Errors.Count.ShouldEqual(1);

            results.Errors[0].PropertyName.ShouldEqual("Accounts[0].AccountName");
        }
        [TestMethod]
        public void Validates_collection_asynchronously()
        {
            var validator = new TestValidator {
                v => v.RuleFor(x => x.Name).NotNull(),
                 v => v.RuleFor(x => x.Accounts).SetCollectionValidator(y => new AsyncAccountValidator(y))
             };

            var results = validator.ValidateAsync(company).Result;
            results.Errors.Count.ShouldEqual(1);

            results.Errors[0].PropertyName.ShouldEqual("Accounts[0].AccountName");
        }
        [TestMethod]
        public void Collection_should_be_explicitly_included_with_expression()
        {
            var validator = new TestValidator {
                v => v.RuleFor(x => x.Name).NotNull(),
                 v => v.RuleFor(x => x.Accounts).SetCollectionValidator(y => new AccountValidator(y))
             };

            var results = validator.Validate(company, x => x.SubCompanies);
            results.Errors.Count.ShouldEqual(0);
        }
        [TestMethod]
        public void Collection_should_be_explicitly_included_with_string()
        {
            var validator = new TestValidator {
                v => v.RuleFor(x => x.Name).NotNull(),
                v => v.RuleFor(x => x.Accounts).SetCollectionValidator(y => new AccountValidator(y))
            };

            var results = validator.Validate(company, "Accounts");
            results.Errors.Count.ShouldEqual(1);
        }
        [TestMethod]
        public void Collection_should_be_excluded()
        {
            var validator = new TestValidator {
                v => v.RuleFor(x => x.Name).NotNull(),
                v => v.RuleFor(x => x.Accounts).SetCollectionValidator(y => new AccountValidator(y))
            };

            var results = validator.Validate(company, x => x.Name);
            results.Errors.Count.ShouldEqual(0);
        }
        [TestMethod]
        public void Condition_should_work_with_child_collection()
        {
            var validator = new TestValidator() {
                v => v.RuleFor(x => x.Accounts).SetCollectionValidator(y => new AccountValidator(y)).When(x => x.Accounts.Count == 4 /*there are only 3*/)
            };

            var result = validator.Validate(company);
            result.IsValid.ShouldBeTrue();
        }
        [TestMethod]
        public void Async_condition_should_work_with_child_collection()
        {
            var validator = new TestValidator() {
                v => v.RuleFor(x => x.Accounts).SetCollectionValidator(y => new AccountValidator(y)).WhenAsync(x => hTask.FromResult(x.Accounts.Count == 4 /*there are only 3*/))
            };

            var result = validator.ValidateAsync(company).Result;
            result.IsValid.ShouldBeTrue();
        }
        [TestMethod]
        public void Skips_null_items()
        {
            var validator = new TestValidator {
                v => v.RuleFor(x => x.Name).NotNull(),
                v => v.RuleFor(x => x.Accounts).SetCollectionValidator(y => new AccountValidator(y))
            };
            company.Accounts[0].AccountName = null;
            company.Accounts[1].AccountName = null;
            var results = validator.Validate(company);
            results.Errors.Count.ShouldEqual(2);
        }
        [TestMethod]
        public void Can_validate_collection_using_validator_for_base_type()
        {
            var validator = new TestValidator() {
                v => v.RuleFor(x => x.Accounts).SetCollectionValidator(y => new AccountInterfaceValidator(y))
            };

            var result = validator.Validate(company);
            result.IsValid.ShouldBeFalse();
        }
        [TestMethod]
        public void Can_specifiy_condition_for_individual_collection_elements()
        {
            var validator = new TestValidator {
                v => v.RuleFor(x => x.Accounts)
                    .SetCollectionValidator(y => new AccountValidator(y))
                    .Where(x => x.Balance == 100)
            };

            var results = validator.Validate(company);
            results.Errors.Count.ShouldEqual(1);
        }
        [TestMethod]
        public void Should_override_property_name()
        {
            var validator = new TestValidator {
                v => v.RuleFor(x => x.Accounts).SetCollectionValidator(y => new AccountValidator(y))
                    .OverridePropertyName("Accounts2")
            };

            var results = validator.Validate(company);
            results.Errors[0].PropertyName.ShouldEqual("Accounts2[0].AccountName");
        }  

        #region InternalClasses

        public class AccountValidator : AbstractValidator<Account>
        {
            public AccountValidator(Company subCompany)
            {
                RuleFor(x => x.AccountName).Must(ValidEmail(subCompany));
            }

            private Func<string, bool> ValidEmail(Company subCompany)
            {
                return accountName => subCompany.Employees.Any(sub => sub.Email == accountName);
            } 
        }
         

        public class AccountInterfaceValidator : AbstractValidator<IAccount>
        {
            public AccountInterfaceValidator(Company subCompany)
            {
                RuleFor(x => x.AccountName).NotEqual(subCompany.Name);
            }
        }

        public class AsyncAccountValidator : AbstractValidator<Account>
        {
            public AsyncAccountValidator(Company subCompany)
            {
                RuleFor(x => x.AccountName).MustAsync(ValidEmail(subCompany));
            }

            private Func<string, CancellationToken, Task<bool>> ValidEmail(Company subCompany)
            {
                return (accountName, cancel) => hTask.FromResult(subCompany.Employees.Any(employee => employee.Email == accountName));
            }
        }

        #endregion
    }
}
